package com.cocotingo.snail;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.GraphicsConfiguration;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D;
import java.awt.image.VolatileImage;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;


public class View implements Iterable<View> {
	
	static long debug_view_alloc_count = 0;
	
	private static Color clearColor = new Color(0x00ffffff, true);
	
	// structure
	private View superView;
	private final List<View> subviews;
	private ViewContext viewContext;
	private int tag;
	
	// position and size
	private int left, top, width, height;
	
	// paint
	private boolean needsRepaint;
	private Color backgroundColor;
	VolatileImage buffer;
	private boolean clipped;
	private boolean hidden;
	private float alpha;
	private boolean paintingEnabled;
	private AffineTransform transform;
	private AffineTransform inversedTransform;
	
	// keyboard focus
	private boolean keyboardFocusable;
	
	public View() {
		this(0, 0, 0, 0);
	}
	
	public View(int left, int top, int width, int height) {
		debug_view_alloc_count++;
		
		// structure
		superView = null;
		subviews = new ArrayList<View>(0);
		viewContext = null;
		tag = 0;
		
		// position and size
		this.left = left;
		this.top = top;
		this.width = width;
		this.height = height;
		
		// paint
		needsRepaint = true;
		backgroundColor = Color.white;
		clipped = false;
		hidden = false;
		alpha = 1.0f;
		paintingEnabled = true;
		transform = inversedTransform = null;
		
		// focus
		keyboardFocusable = false;
		
	}
	
	public final ViewContext getViewContext() {
		return viewContext;
	}

	static final void setViewContext(View view, ViewContext viewContext) {
		view.viewContext = viewContext;
		if (viewContext != null) viewContext.invalid();
		for (View child : view) {
			setViewContext(child, viewContext);
		}
	}

	//// Structure-Related Methods ////
	
	public final View getSuperView() {
		return superView;
	}

	public final View[] getSubviews() {
		return subviews.toArray(new View[0]);
	}
	
	public final <T> T[] getSubviews(T[] array) {
		return subviews.toArray(array);
	}
	
	private class ViewIterator implements ListIterator<View> {
		
		private final ListIterator<View> it;
		
		public ViewIterator(ListIterator<View> it) {
			this.it = it;
		}

		@Override
		public void add(View e) {
			throw new UnsupportedOperationException();
		}

		@Override
		public boolean hasNext() {
			return it.hasNext();
		}

		@Override
		public boolean hasPrevious() {
			return it.hasPrevious();
		}

		@Override
		public View next() {
			return it.next();
		}

		@Override
		public int nextIndex() {
			return it.nextIndex();
		}

		@Override
		public View previous() {
			return it.previous();
		}

		@Override
		public int previousIndex() {
			return it.previousIndex();
		}

		@Override
		public void remove() {
			throw new UnsupportedOperationException();
		}

		@Override
		public void set(View e) {
			throw new UnsupportedOperationException();
		}
		
	}
	
	public final Iterator<View> iterator() {
		return listIterator();
	}
	
	public final ListIterator<View> listIterator() {
		return new ViewIterator(subviews.listIterator());
	}
	
	public final ListIterator<View> listIterator(int index) {
		return new ViewIterator(subviews.listIterator(index));
	}
	
	public final View getSubview(int index) {
		return subviews.get(index);
	}
	
	public final View addSubview(View v) {
		return addSubview(v, subviews.size());
	}

	public View addSubview(View v, int index) {
		if (v.superView != null) {
			throw new IllegalArgumentException("the view already has a super view.");
		} else if (index < 0 || index > subviews.size()) {
			throw new IllegalArgumentException("invalid view index");
		}
		v.superView = this;
		View.setViewContext(v, this.viewContext);
		subviews.add(index, v);
		if (viewContext != null) viewContext.invalid();
		subviewAdded(v);
		return this;
	}

	public void removeFromSuperView() {
		if (superView != null) {
			turnBackBuffer(this);
			if (viewContext != null && viewContext.isInFocusChain(this)) {
				viewContext.requestFocus(null);
			}
			superView.subviews.remove(this);
			superView.subviewRemoved(this);
			if (superView.viewContext != null) superView.viewContext.invalid();
			superView = null;
			setViewContext(this, null);
		} else {
			throw new IllegalStateException("the view does not have a super view.");
		}
	}
	
	private void turnBackBuffer(View view) {
		if (view.buffer != null) {
			viewContext.bufferPool.turnBackBuffer(view.buffer);
			view.buffer = null;
		}
		for (View v : view) {
			turnBackBuffer(v);
		}
	}
	
	public final void removeAllSubviews() {
		while (count() > 0) {
			getSubview(0).removeFromSuperView();
		}
	}

	public final View[] getViewHierarchy() {
		LinkedList<View> stack = new LinkedList<View>();
		View view = this;
		while (view != null) {
			stack.addFirst(view);
			view = view.superView;
		}
		return stack.toArray(new View[stack.size()]);
	}

	public int getTag() {
		return tag;
	}
	
	public void setTag(int tag) {
		this.tag = tag;
	}
	
	public View getTaggedSubview(int tag) {
		for (View view : subviews) {
			if (view.tag == tag) {
				return view;
			}
		}
		return null;
	}
	
	public View getTaggedSubviewDeeply(int tag) {
		return getTaggedSubviewDeeply(this, tag);
	}
	
	private static View getTaggedSubviewDeeply(View target, int tag) {
		for (View view : target.subviews) {
			if (view.tag == tag) return view;
		}
		for (View view : target.subviews) {
			return getTaggedSubviewDeeply(view, tag);
		}
		return null;
	}
	
	public final int getSubviewIndex(View view) {
		return subviews.indexOf(view);
	}
	
	public void setSubviewIndex(View view, int targetIndex) {
		subviews.remove(view);
		subviews.add(targetIndex, view);
		if (viewContext != null) viewContext.invalid();
	}
	
	public final void setSubviewIndex(int index, int targetIndex) {
		setSubviewIndex(subviews.get(index), targetIndex);
	}
	
	public final int getIndex() {
		return superView.getSubviewIndex(this);
	}
	
	public final void setIndex(int index) {
		superView.setSubviewIndex(this, index);
	}
	
	public final int count() {
		return subviews.size();
	}
	
	//// Position & Size ////
	
	public final int getLeft() {
		return left;
	}
	
	public final int getTop() {
		return top;
	}
	
	public final int getWidth() {
		return width;
	}
	
	public final int getHeight() {
		return height;
	}
	
	public final int getRight() {
		return superView.width - left - width;
	}
	
	public final int getBottom() {
		return superView.height - top - height;
	}
	
	
	public final View setLeft(int left) {
		return setPosition(left, top);
	}
	
	public final View setTop(int top) {
		return setPosition(left, top);
	}
	
	public final View setWidth(int width) {
		return setSize(width, height);
	}
	
	public final View setHeight(int height) {
		return setSize(width, height);
	}
	
	public View setPosition(int left, int top) {
		this.left = left;
		this.top = top;
		if (viewContext != null) viewContext.invalid();
		return this;
	}
	
	public final View setPosition(Vector pos) {
		return setPosition(pos.x, pos.y);
	}
	
	public final Vector getPosition() {
		return new Vector(left, top);
	}
	
	public final Vector getPositionInRootView() {
		Vector pos = getPosition();
		View[] views = getViewHierarchy();
		for (int i = views.length - 2; i >= 0; i--) {
			pos.increase(views[i].getPosition());
		}
		return pos;
	}
	
	public View setSize(int width, int height) {
		if (width == this.width && height == this.height) return this;
		this.width = width;
		this.height = height;
		needsRepaint = true;
		if (viewContext != null) viewContext.invalid();
		return this;
	}
	
	public final View setSize(Vector size) {
		return setSize(size.x, size.y);
	}
	
	public final Vector getSize() {
		return new Vector(width, height);
	}
	
	public final View setFrame(int left, int top, int width, int height) {
		return setPosition(left, top).setSize(width, height);
	}
	
	//// Static Layout Methods ////
	
	public static void putViewWithLeft(View view, int left) {
		view.setLeft(left);
	}
	
	public static void putViewWithLeft(View view, View ref) {
		view.setLeft(ref.left);
	}

	public static void putViewWithTop(View view, int top) {
		view.setTop(top);
	}
	
	public static void putViewWithTop(View view, View ref) {
		view.setTop(ref.top);
	}

	public static void putViewWithRight(View view, int right) {
		view.setLeft(view.superView.width - right - view.width);
	}
	
	public static void putViewWithRight(View view, View ref) {
		view.setLeft(view.superView.width - (ref.left + ref.width) - view.width);
	}

	public static void putViewWithBottom(View view, int bottom) {
		view.setTop(view.superView.height - bottom - view.height);
	}
	
	public static void putViewWithBottom(View view, View ref) {
		view.setTop(view.superView.height - (ref.top + ref.height) - view.height);
	}

	public static void putViewWithHorizontalCenter(View view, int hc) {
		view.setLeft(hc - view.width / 2);
	}
	
	public static void putViewWithHorizontalCenter(View view, View ref) {
		view.setLeft(ref.left + ref.width / 2 - view.width / 2);
	}

	public static void putViewWithVerticalCenter(View view, int vc) {
		view.setTop(vc - view.height / 2);
	}
	
	public static void putViewWithVerticalCenter(View view, View ref) {
		view.setTop(ref.top + ref.height / 2 - view.height / 2);
	}
	
	public static void putViewWithLeftAndTop(View view, int left, int top) {
		view.setPosition(left, top);
	}
	
	public static void putViewWithLeftAndBottom(View view, int left, int bottom) {
		view.setPosition(left, view.superView.height - bottom - view.height);
	}
	
	public static void putViewWithLeftAndVerticalCenter(View view, int left, int vc) {
		view.setPosition(left, vc - view.height / 2);
	}

	public static void putViewWithRightAndTop(View view, int right, int top) {
		view.setPosition(view.superView.width - right - view.width, top);
	}

	public static void putViewWithRightAndBottom(View view, int right, int bottom) {
		view.setPosition(view.superView.width - right - view.width, view.superView.height - bottom - view.height);
	}
	
	public static void putViewWithRightAndVerticalCenter(View view, int right, int vc) {
		view.setPosition(view.superView.width - right - view.width, vc - view.height / 2);
	}
	
	public static void putViewWithHorizontalCenterAndTop(View view, int hc, int top) {
		view.setPosition(hc - view.width / 2, top);
	}

	public static void putViewWithHorizontalCenterAndBottom(View view, int hc, int bottom) {
		view.setPosition(hc - view.width / 2, view.superView.height - bottom - view.height);
	}
	
	public static void putViewWithHorizontalCenterAndVerticalCenter(View view, int hc, int vc) {
		view.setPosition(hc - view.width / 2, vc - view.height / 2);
	}
	
	public static void putViewAtLeftSideOfView(View view, View ref, int margin) {
		view.setLeft(ref.left - margin - view.width);
	}
	
	public static void putViewAtRightSideOfView(View view, View ref, int margin) {
		view.setLeft(ref.left + ref.width + margin);
	}
	
	public static void putViewAtTopSideOfView(View view, View ref, int margin) {
		view.setTop(ref.top - margin - view.height);
	}
	
	public static void putViewAtBottomSideOfView(View view, View ref, int margin) {
		view.setTop(ref.top + ref.height + margin);
	}
	
	public static void putViewAtCenterOfView(View view, View ref) {
		putViewWithHorizontalCenterAndVerticalCenter(view, ref.left + ref.width / 2, ref.top + ref.height / 2);
	}
	
	public static void putViewAtCenterOfSuperView(View view) {
		putViewWithHorizontalCenterAndVerticalCenter(view, view.superView.width / 2, view.superView.height / 2);
	}
	
	public static void scaleViewWithLeft(View view, int left) {
		int delta = view.left - left;
		view.setLeft(left);
		view.setWidth(view.width + delta);
	}
	
	public static void scaleViewWithLeft(View view, View ref) {
		int delta = view.left - ref.left;
		view.setLeft(ref.left);
		view.setWidth(view.width + delta);
	}
	
	public static void scaleViewWithRight(View view, int right) {
		view.setWidth(view.superView.width - right - view.left);
	}
	
	public static void scaleViewWithRight(View view, View ref) {
		view.setWidth(view.superView.width - (ref.superView.width - ref.left - ref.width) - view.left);
	}
	
	public static void scaleViewWithTop(View view, int top) {
		int delta = view.top - top;
		view.setTop(top);
		view.setHeight(view.height + delta);
	}
	
	public static void scaleViewWithTop(View view, View ref) {
		int delta = view.top - ref.top;
		view.setTop(ref.top);
		view.setHeight(view.height + delta);
	}
	
	public static void scaleViewWithBottom(View view, int bottom) {
		view.setHeight(view.superView.height - bottom - view.top);
	}
	
	public static void scaleViewWithBottom(View view, View ref) {
		view.setHeight(view.superView.height - (ref.superView.height - ref.top - ref.height) - view.top);
	}
	
	public static void scaleViewWithLeftAndTop(View view, int left, int top) {
		scaleViewWithLeft(view, left);
		scaleViewWithTop(view, top);
	}
	
	public static void scaleViewWithLeftAndBottom(View view, int left, int bottom) {
		scaleViewWithLeft(view, left);
		scaleViewWithBottom(view, bottom);
	}
	
	public static void scaleViewWithRightAndTop(View view, int right, int top) {
		scaleViewWithRight(view, right);
		scaleViewWithTop(view, top);
	}
	
	public static void scaleViewWithRightAndBottom(View view, int right, int bottom) {
		scaleViewWithRight(view, right);
		scaleViewWithBottom(view, bottom);
	}
	
	public static void scaleViewWithLeftAndRight(View view, int left, int right) {
		view.setLeft(left);
		scaleViewWithRight(view, right);
	}
	
	public static void scaleViewWithTopAndBottom(View view, int top, int bottom) {
		view.setTop(top);
		scaleViewWithBottom(view, bottom);
	}
	
	public static void scaleViewWithMarginToSuperView(View view, int margin) {
		view.setPosition(margin, margin);
		scaleViewWithRightAndBottom(view, margin, margin);
	}
	
	//// Core Painting Methods ////
	
	private void createBuffer() {
		if (buffer != null) {
			viewContext.bufferPool.turnBackBuffer(buffer);
		}
		buffer = viewContext.bufferPool.getBuffer(width, height);
	}
	
	private void createBiggerBuffer() {
		if (buffer != null) {
			viewContext.bufferPool.turnBackBuffer(buffer);
		}
		buffer = viewContext.bufferPool.getBuffer(width + (width >> 3), height + (height >> 3));
	}
	
	protected final GraphicsX createTemporaryGraphicsContext() {
		if (viewContext != null) return viewContext.getGraphics();
		else return ViewContext.getSharedGraphics();
	}
	
	private void repaintBuffer(boolean clearNeedRepaintFlag) {
		do {
			if (buffer.validate(getGraphicsConfiguration()) == VolatileImage.IMAGE_INCOMPATIBLE) {
				createBuffer();
			}
			GraphicsX g = new GraphicsX(buffer.createGraphics());
			
			// clear current content
			g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC));
			g.setColor(backgroundColor == null ? clearColor : backgroundColor);
			g.fillRect(0, 0, width, height);
			g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
			
			// repaint
			repaintView(g);

			g.dispose();
		} while (buffer.contentsLost());
		if (clearNeedRepaintFlag) needsRepaint = false;
	}
	
	final void repaint(GraphicsX g, boolean clearNeedRepaintFlag) {
		if (hidden) {
			return;
		}
		
		final boolean no_size = width <= 0 || height <= 0;
		
		// translate
		g.translate(left, top);
		
		// transform
		if (transform != null) g.transform(transform);
		
		// clip
		Shape clipShape = null;
		if (this.clipped && !no_size) {
			clipShape = g.getClip();
			g.clipRect(0, 0, width, height);
		}
		
		// alpha
		final float old_alpha = g.getCurrentAlpha();
		g.setCurrentAlpha(old_alpha * alpha);

		// repaint self
		if (!no_size && paintingEnabled) {
			if (buffer == null) createBuffer();
			do {
				int returnCode = buffer.validate(getGraphicsConfiguration());
				if (returnCode == VolatileImage.IMAGE_RESTORED ||
					returnCode == VolatileImage.IMAGE_INCOMPATIBLE) {
					// contents need to be restored/recreated
					repaintBuffer(clearNeedRepaintFlag); // restored/recreate contents
				} else if (needsRepaint) {
					// check buffer size
					if (buffer.getWidth() < width || buffer.getHeight() < height) createBiggerBuffer();
					repaintBuffer(clearNeedRepaintFlag);
				}
				
				// on screen
				g.drawImage(buffer, 0, 0, width, height, 0, 0, width, height, null);
			} while (buffer.contentsLost());
		}
		
		if (subviews.size() > 0 && (!this.clipped || !no_size)) {
			// repaint children
			for (int i = 0; i < subviews.size(); i++) {
				subviews.get(i).repaint(g, clearNeedRepaintFlag);
			}
		}
		
		// restore
		g.setCurrentAlpha(old_alpha);
		if (clipShape != null) {
			g.setClip(clipShape);
		}
		if (inversedTransform != null) g.transform(inversedTransform);
		g.translate(-left, -top);
	}
	
	public final void paintOnCustomizedTarget(GraphicsX g) {
		repaint(g, false);
	}
	
	//// Painting-Related Methods ////
	
	public final void ensureBufferSize(int width, int height) {
		width = Math.max(getWidth(), width);
		height = Math.max(getHeight(), height);
		if (buffer != null) {
			if (buffer.getWidth() < width || buffer.getHeight() < height) {
				viewContext.bufferPool.turnBackBuffer(buffer);
				buffer = viewContext.bufferPool.getBuffer(width, height);
				needsRepaint = true;
				if (viewContext != null) viewContext.invalid();
			} else {
				return;
			}
		}
	}

	public final void trimBufferSize() {
		createBuffer();
		needsRepaint = true;
		if (viewContext != null) viewContext.invalid();
	}

	public GraphicsConfiguration getGraphicsConfiguration() {
		return viewContext.getGraphicsConfiguration();
	}

	public final View setNeedsRepaint() {
		needsRepaint = true;
		if (viewContext != null) viewContext.invalid();
		return this;
	}

	public final Color getBackgroundColor() {
		return backgroundColor;
	}

	public View setBackgroundColor(Color backgroundColor) {
		this.backgroundColor = backgroundColor;
		needsRepaint = true;
		if (viewContext != null) viewContext.invalid();
		return this;
	}

	public final boolean isClipped() {
		return clipped;
	}

	public View setClipped(boolean clipped) {
		this.clipped = clipped;
		if (viewContext != null) viewContext.invalid();
		return this;
	}

	public final boolean isHidden() {
		return hidden;
	}
	
	public View setHidden(boolean hidden) {
		this.hidden = hidden;
		if (hidden == true && viewContext != null && viewContext.isInFocusChain(this)) {
			viewContext.requestFocus(null);
		}
		if (viewContext != null) viewContext.invalid();
		return this;
	}
	
	public final float getAlpha() {
		return alpha;
	}
	
	public View setAlpha(float alpha) {
		this.alpha = alpha;
		if (viewContext != null) viewContext.invalid();
		return this;
	}
	
	public final boolean isPaintingEnabled() {
		return paintingEnabled;
	}

	public void setPaintingEnabled(boolean paintingEnable) {
		this.paintingEnabled = paintingEnable;
		if (!paintingEnable && buffer != null) {
			viewContext.bufferPool.turnBackBuffer(buffer);
			buffer = null;
		}
		if (viewContext != null) viewContext.invalid();
	}
	
	public final AffineTransform getTransform() {
		return transform == null ? null : (AffineTransform) transform.clone();
	}
	
	public final void setTransform(AffineTransform transform) {
		if (transform == null) {
			this.transform = null;
			this.inversedTransform = null;
		} else {
			this.transform = (AffineTransform) transform.clone();
			try {
				this.inversedTransform = this.transform.createInverse();
			} catch (NoninvertibleTransformException e) {
				throw new IllegalArgumentException(e);
			}
		}
		if (viewContext != null) viewContext.invalid();
	}
	
	//// Mouse Behavior ////
	
	public final Vector transformPointFromSuperView(Vector v) {
		v = new Vector(v.x - left, v.y - top);
		if (inversedTransform != null) {
			Point2D p = inversedTransform.transform(new Point2D.Float(v.x, v.y), null);
			v.x = (int) p.getX();
			v.y = (int) p.getY();
		}
		return v;
	}
	
	final boolean isInsideWithTransform(Vector v) {
		return isInside(transformPointFromSuperView(v));
	}
	
	public boolean isInside(Vector v) {
		return v.x >= 0 && v.x < width && v.y >= 0 && v.y < height;
	}
	
	//// Keyboard Focus ////

	public final boolean isKeyboardFocusable() {
		return keyboardFocusable;
	}

	public View setKeyboardFocusable(boolean focusable) {
		this.keyboardFocusable = focusable;
		if (focusable == false && viewContext != null && viewContext.isInFocusChain(this)) {
			viewContext.requestFocus(null);
		}
		return this;
	}

	public boolean isKeyboardFocus() {
		return keyboardFocusable && viewContext != null && viewContext.isFocus(this);
	}
	
	public boolean isInKeyboardFocusHierarchy() {
		return keyboardFocusable && viewContext != null && viewContext.isInFocusChain(this);
	}

	public void requestKeyboardFocus() {
		if (keyboardFocusable && viewContext != null && !hidden && !isKeyboardFocus()) {
			viewContext.requestFocus(this);
		}
	}

	public void resignKeyboardFocus() {
		if (isKeyboardFocus()) viewContext.requestFocus(null);
	}

	//// Rendering & Painting Handlers ////

	protected void repaintView(GraphicsX g) {}
	
	//// Structure Changing Handlers ////

	protected void subviewAdded(View subview) {}
	protected void subviewRemoved(View subView) {}
	
	//// Keyboard Event Handlers ////

	protected void gotKeyboardFocus() {}
	protected void lostKeyboardFocus() {}
	
	protected void preKeyTyped(KeyEvent e) {}
	protected void preKeyPressed(KeyEvent e) {}
	protected void preKeyReleased(KeyEvent e) {}
	
	protected void postKeyTyped(KeyEvent e) {}
	protected void postKeyPressed(KeyEvent e) {}
	protected void postKeyReleased(KeyEvent e) {}
	
	//// Mouse Event Handlers ////
	
	protected void mouseEntered() {}
	protected void mouseExited() {}
	
	protected void preMousePressed(MouseEvent e) {}
	protected void preMouseReleased(MouseEvent e) {}
	protected void preMouseClicked(MouseEvent e) {}
	protected void preMouseMoved(MouseEvent e) {}
	protected void preMouseDragged(MouseEvent e) {}
	protected void preMouseWheelMoved(MouseWheelEvent e) {}

	protected void postMousePressed(MouseEvent e) {}
	protected void postMouseReleased(MouseEvent e) {}
	protected void postMouseClicked(MouseEvent e) {}
	protected void postMouseMoved(MouseEvent e) {}
	protected void postMouseDragged(MouseEvent e) {}
	protected void postMouseWheelMoved(MouseWheelEvent e) {}
	
}

